package com.sca.point.service.impl;

import com.sca.point.entity.AbstractEntity;
import com.sca.point.entity.api.BaseResult;
import com.sca.point.entity.enums.ResultEnum;
import com.sca.point.service.CacheLoadble;
import com.sca.point.utils.RedisBloomFilter;
import com.sca.point.utils.RedisLockUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

/**
 * 失效：应用程序先从cache取数据，没有得到，则从数据库中取数据，成功后，放到缓存中。
 * 命中：应用程序从cache中取数据，取到后返回。
 * 更新：先把数据存到数据库中，成功后，再让缓存失效。
 * <p>
 * db缓存双写一致性：
 * 方案1：先更新数据，删除缓存，延时后（异步后）再删除缓存。当读比写慢时依然会有赃数据，但几率较小，不考虑
 * 方案2：阿里canal，订阅binlog，更新缓存。
 *
 * @author liuxianfei
 * @since 2020/12/7 11:55
 */
@Slf4j
@Service
public class CacheTemplate<T extends AbstractEntity<T>> {

    private final RedisBloomFilter redisBloomFilter;
    private final RedisLockUtil redisLockUtil;
    private final RedisTemplate<String, Object> redisTemplate;

    public CacheTemplate(RedisBloomFilter redisBloomFilter, RedisLockUtil redisLockUtil, RedisTemplate<String, Object> redisTemplate) {
        this.redisBloomFilter = redisBloomFilter;
        this.redisLockUtil = redisLockUtil;
        this.redisTemplate = redisTemplate;
    }

    public BaseResult<?> redisFindCache(String key, long expire, TimeUnit unit, CacheLoadble<T> cacheLoadble, boolean b) {
        // 先走布隆过滤器
        if (b) {
            boolean exist = redisBloomFilter.mightContain(key);
            if (!exist) {
                return new BaseResult<>(ResultEnum.ERR_PARAM, "布隆过滤器筛选");
            }
        }
        // 查询缓存
        Object o = redisTemplate.opsForValue().get(key);
        if (o != null) {
            return new BaseResult<>(o);
        }
        try {
            redisLockUtil.lock(key);
            o = redisTemplate.opsForValue().get(key);
            if (o != null) {
                return new BaseResult<>(o);
            }
            T data = cacheLoadble.load();
            if (data != null) {
                redisTemplate.opsForValue().set(key, data, expire, unit);
                return new BaseResult<>(o);
            }
            return new BaseResult<>(ResultEnum.ERR_PARAM, "无此数据");
        } finally {
            redisLockUtil.unLock(key);
        }
    }

}
